#pragma  once
#include "concepts.h"
#include "task.h"
#include "timer_loop.h"
#include "when_any.h"
#include <cerrno>
#include <chrono>
#include <coroutine>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <span>
#include <string>
#include <sys/epoll.h>
#include <unistd.h>
#include <utility>
#include <variant>

namespace co_async {

struct EpollLoop;

struct EpollPromise : Promise<void> {
  auto get_return_object() {
    return std::coroutine_handle<EpollPromise>::from_promise(*this);
  }
  EpollPromise &operator=(EpollPromise &&) = delete;
  ~EpollPromise();
  int fd;
  uint32_t events;
  EpollLoop *loop;
};

struct EpollLoop {
  void add_listener(EpollPromise &promise) {
    epoll_event event;
    event.events = promise.events;
    event.data.ptr = &promise;
    epoll_ctl(epfd, EPOLL_CTL_ADD, promise.fd, &event);
    count++;
  }

  void remove_listener(EpollPromise &promise) {
    epoll_ctl(epfd, EPOLL_CTL_DEL, promise.fd, nullptr);
    count--;
  }

  void run_once(std::optional<std::chrono::system_clock::duration> timeout =
               std::nullopt) {
    int timeout_ms =
        timeout
            ? std::chrono::duration_cast<std::chrono::milliseconds>(*timeout)
                  .count()
            : -1;
    int res = check_error(epoll_wait(epfd, events, 64, timeout_ms));
    for (int i = 0; i < res; i++) {
      auto &promise = *(EpollPromise *)events[i].data.ptr;
      std::coroutine_handle<EpollPromise>::from_promise(promise).resume();
    }
  }
  ~EpollLoop() { close(epfd); }
  bool has_event() { return count > 0; }

  int epfd = epoll_create1(0);
  epoll_event events[64];
  std::size_t count = 0;
};

struct EpollAwaiter {
  bool await_ready() { return false; }
  auto await_suspend(std::coroutine_handle<EpollPromise> handle) {
    EpollPromise &promise = handle.promise();
    promise.loop = loop;
    promise.fd = fd;
    promise.events = events;
    loop->add_listener(handle.promise());
  }
  void await_resume() {}
  EpollLoop *loop;
  int fd;
  uint32_t events;
};

inline EpollPromise::~EpollPromise() { loop->remove_listener(*this); }

inline Task<void, EpollPromise> wait_file(EpollLoop *loop, int fd,
                                          uint32_t events) {
  co_await EpollAwaiter{loop, fd, events};
  co_return;
}

// inline std::size_t read_file(int fd, std::span<char> buffer) {
//   auto len = read(fd, buffer.data(), buffer.size());
//   if (len == -1) {
//     if (errno != EAGAIN) [[unlikely]] {
//       throw std::system_error(errno, std::system_category());
//     }
//   }
//   return len;
// }

inline Task<std::size_t> read_file(EpollLoop *loop, int fd, char *buffer,
                                   std::size_t size) {
  co_await wait_file(loop, fd, EPOLLIN);
  co_return check_error(read(fd, buffer, size));
}

inline Task<std::size_t> read_file(EpollLoop *loop, int fd,
                                   std::span<char> buffer) {
  co_await wait_file(loop, fd, EPOLLIN);
  co_return check_error(read(fd, buffer.data(), buffer.size()));
}

inline Task<std::string> read_string(EpollLoop *loop, int fd) {
  co_await wait_file(loop, fd, EPOLLIN | EPOLLET);
  std::string s;
  std::size_t chunk = 8;

  while (true) {
    auto exist = s.size();
    s.resize(exist + chunk);

    auto len = read(fd, s.data() + exist, chunk);
    if (len == -1) {
      if (errno != EAGAIN) [[unlikely]] {
        throw std::system_error(errno, std::system_category());
      }
    }
    if (len != chunk) {
      s.resize(exist + len);
      break;
    }
    if (chunk < 65536) {
      chunk *= 4;
    }
  }

  co_return s;
}

template <Awaitable A, class Rep, class Period>
Task<std::optional<typename AwaitableTraits<A>::NonVoidRetType>>
time_limit(TimerLoop &loop, A &&a, std::chrono::duration<Rep, Period> dur) {
  auto v = co_await when_any(std::forward<A>(a), sleep_for(loop, dur));
  if (auto *ret = std::get_if<0>(&v)) {
    co_return std::move(*ret);
  } else {
    co_return std::nullopt;
  }
}

} // namespace co_async